/**
 * 认证模型
 *
 * Created by snowingsea on 2020/01/21.
 */
const moment = require('moment');
const mongoose = require('mongoose');
const Token = require('./mongo/token');
const encrypt = require('./encrypt');
const heartbeat = require('./heartbeat');
const license = require('./license');
const users = require('./users');
const PhoenixError = require('./phoenix-error');

/**
 * 获取令牌json信息
 * @param token 令牌信息
 * @return {Object}
 */
function getTokenJson(token) {
  return {
    accessToken: token.accessToken,
    refreshToken: token.refreshToken,
    expiresIn: global.config.auth.expiresIn,
  };
}

/**
 * 获取访问令牌
 * @param {String} accessToken 访问令牌
 * @return {Promise}
 */
function getToken(accessToken) {
  return Token.findOne({ accessToken });
}

async function removeExceededTesterToken(upperLimit) {
  const testerTokens = await Token.aggregate([
    {
      $lookup: {
        from: 'users', localField: 'userId', foreignField: '_id', as: 'user',
      },
    },
    {
      $unwind: '$user',
    },
    {
      $match: {
        'user.role': { $bitsAllSet: users.roleKind.tester },
      },
    },
    {
      $lookup: {
        from: 'heartbeats', localField: 'userId', foreignField: 'userId', as: 'alive',
      },
    },
    {
      $set: {
        alive: { $toBool: { $size: '$alive' } },
      },
    },
    {
      $sort: {
        alive: -1, // 当前在线的优先保留
        expires: -1, // 过期时间久的优先保留
      },
    },
    {
      $project: {
        _id: 1,
      },
    },
  ]);

  if (testerTokens.length > upperLimit) {
    const deleteIds = testerTokens.slice(upperLimit).map(({ _id }) => _id);
    await Token.deleteMany({ _id: { $in: deleteIds } });
  }
}

/**
 * 生成访问令牌
 * @param {String} userId 用户Id
 * @return {Promise}
 */
async function generateAccessToken(userId) {
  let token = await Token.findOne({ userId });
  if (global.config.auth.multiUserLogin && token) {
    return token;
  }

  const user = await users.getUser(userId);
  const isTester = (user.role & users.roleKind.tester) > 0;

  if (isTester) {
    // tester用户需要判断在线人数限制
    const lice = await license.getLicense();
    const userLimit = lice && lice.userLimit ? lice.userLimit : 0;
    const onlines = await heartbeat.getTesterOnlineCount();
    if (onlines >= userLimit) {
      // 已达在线人数上限
      throw new PhoenixError('conflict', 'Reached online limit');
    }
    // 确保当前总tester token数不超过tester用户数量上限
    await removeExceededTesterToken(userLimit - 1); // 多减去1个给当前登录用户腾出位置
  }
  await Token.deleteMany({ userId });
  const [accessToken, refreshToken] = await Promise.all([
    encrypt.generateRandomToken(),
    encrypt.generateRandomToken(),
  ]);
  token = new Token({
    accessToken,
    refreshToken,
    userId: mongoose.Types.ObjectId(userId),
    expires: moment().add(global.config.auth.expiresIn, 'seconds'),
  });
  await token.save();
  return token;
}

/**
 * 删除用户token
 * @param {String} userId 用户id
 * @return {Promise}
 */
function removeTokens(userId) {
  return new Promise((resolve, reject) => {
    Token.deleteOne({ userId: mongoose.Types.ObjectId(userId) })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * Export model definition object.
 */
module.exports = {
  getTokenJson,
  getToken,

  generateAccessToken,
  removeTokens,

  removeExceededTesterToken,
};
exports = Object.assign(exports, module.exports);
